<?php

namespace App\Common\Tools;

use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use GuzzleHttp\Exception\RequestException;

/**
 * HTTP 请求工具类
 * 基于 Guzzle HTTP Client 封装.
 */
class RequestTools
{
    protected Client $client;
    protected array $config = [
        'timeout'         => 30,
        'connect_timeout' => 30,
        'verify'          => false, // SSL 验证
        'http_errors'     => true, // 是否抛出 HTTP 错误
        'log_enabled'     => true,
        'proxy_enabled'   => false,
        'proxy_strategy'  => 'random', // random, round_robin, failover
        'retry_times'     => 3,
        'retry_sleep'     => 100,
    ];

    protected array $proxyPool      = [];
    protected int $proxyIndex       = 0;
    protected ?string $currentProxy = null;
    protected array $defaultHeaders = [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept'     => 'application/json',
    ];

    protected array $cookies = [];
    protected array $headers = [];

    /**
     * 构造函数.
     */
    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);
        $this->initClient();
        $this->initProxyPool();
    }

    /**
     * 初始化 HTTP 客户端.
     */
    protected function initClient(): void
    {
        $this->client = new Client([
            'timeout'         => $this->config['timeout'],
            'connect_timeout' => $this->config['connect_timeout'],
            'verify'          => $this->config['verify'],
            'http_errors'     => $this->config['http_errors'],
        ]);
    }

    /**
     * 初始化代理池.
     */
    protected function initProxyPool(): void
    {
        if ($this->config['proxy_enabled']) {
            $proxyPoolStr       = config('app.ip_proxy_pool', '');
            $proxyPoolOutStr    = config('app.ip_proxy_pool_out', '');
            $this->proxyPool    = array_merge(explode(',', $proxyPoolStr), explode(',', $proxyPoolOutStr));

            if (empty($this->proxyPool)) {
                Log::warning('代理池已启用但为空');
                $this->config['proxy_enabled'] = false;
            }
        }
    }

    /**
     * 创建新实例.
     */
    public static function make(array $config = []): self
    {
        return new static($config);
    }

    /**
     * 发送 GET 请求
     * 优化：合并URL中的参数和传入的参数.
     */
    public function get(string $url, array $params = [], array $options = [])
    {
        // 解析URL中已有的参数
        $urlParts       = parse_url($url);
        $existingParams = [];

        if (isset($urlParts['query'])) {
            parse_str($urlParts['query'], $existingParams);
        }

        // 合并参数（传入的参数优先级更高）
        $mergedParams = array_merge($existingParams, $params);

        // 重新构建URL（去掉原来的query部分）
        $newUrl = $url;
        if (isset($urlParts['query'])) {
            $newUrl = str_replace('?' . $urlParts['query'], '', $newUrl);
        }

        // 如果有合并后的参数，添加到URL
        if (!empty($mergedParams)) {
            $queryString = http_build_query($mergedParams);
            $newUrl .= (false === strpos($newUrl, '?') ? '?' : '&') . $queryString;
        }

        return $this->requestHandle('GET', $newUrl, $options);
    }

    /**
     * 发送 POST 请求（表单格式）.
     * 优化：处理URL中的参数.
     */
    public function post(string $url, array $data = [], array $options = [])
    {
        // 提取URL中的参数到options
        $urlWithParams = $this->extractParamsFromUrl($url);
        $options       = array_merge($urlWithParams['options'], $options);

        $options['form_params'] = $data;

        return $this->requestHandle('POST', $urlWithParams['url'], $options);
    }

    /**
     * 发送 JSON POST 请求
     * 优化：处理URL中的参数.
     */
    public function postJson(string $url, array $data = [], array $options = [])
    {
        // 提取URL中的参数到options
        $urlWithParams = $this->extractParamsFromUrl($url);
        $options       = array_merge($urlWithParams['options'], $options);

        $options['json']    = $data;
        $options['headers'] = array_merge($options['headers'] ?? [], [
            'Content-Type' => 'application/json',
        ]);

        return $this->requestHandle('POST', $urlWithParams['url'], $options);
    }

    /**
     * 从URL中提取参数到options.
     */
    protected function extractParamsFromUrl(string $url): array
    {
        $urlParts = parse_url($url);
        $result   = [
            'url'     => $url,
            'options' => [],
        ];

        if (isset($urlParts['query'])) {
            // 解析URL中的参数
            parse_str($urlParts['query'], $queryParams);

            // 将参数移动到options中
            $result['options']['query'] = $queryParams;

            // 重新构建URL（去掉query部分）
            $result['url'] = str_replace('?' . $urlParts['query'], '', $url);
        }

        return $result;
    }

    /**
     * 发送 Multipart POST 请求（文件上传）.
     */
    public function postMultipart(string $url, array $data = [], array $options = []): array
    {
        $multipart = [];
        foreach ($data as $name => $value) {
            $multipart[] = [
                'name'     => $name,
                'contents' => $value,
            ];
        }
        $options['multipart'] = $multipart;

        return $this->request('POST', $url, $options);
    }

    /**
     * 发送请求
     */
    public function request(string $method, string $url, array $options = [])
    {
        $method = strtoupper($method);
        if ('GET' == $method) {
            return $this->get($url, $options);
        } elseif ('POST' == $method) {
            return $this->post($url, $options);
        } else {
            throw_if(true, '不支持的请求方法');
        }

        return [];
    }

    /**
     * 发送请求
     */
    public function requestHandle(string $method, string $url, array $options = [])
    {
        $startTime   = microtime(true);
        $proxy       = $this->getProxy();
        $attempt     = 0;
        $maxAttempts = $this->config['retry_times'];

        do {
            try {
                ++$attempt;
                $finalOptions = $this->prepareOptions($options, $proxy);

                $response = $this->client->request($method, $url, $finalOptions);

                $result = $this->handleResponse($response);
                $this->logRequest($method, $url, $options, $result, $startTime, $proxy, $attempt);

                return $result['data'] ?? $result;
            } catch (RequestException $e) {
                if ($attempt >= $maxAttempts) {
                    $this->handleRequestError($method, $url, $options, $e, $startTime, $proxy, $attempt);
                    throw $e;
                }

                // 重试前等待
                usleep($this->config['retry_sleep'] * 1000);
            }
        } while ($attempt < $maxAttempts);

        throw new \Exception('Request failed after ' . $maxAttempts . ' attempts');
    }

    /**
     * 异步并发请求
     */
    public function concurrent(array $requests): array
    {
        $promises = [];
        $results  = [];

        foreach ($requests as $key => $request) {
            $method  = $request['method'] ?? 'GET';
            $url     = $request['url'];
            $options = $request['options'] ?? [];
            $proxy   = $this->getProxy();

            $finalOptions   = $this->prepareOptions($options, $proxy);
            $promises[$key] = $this->client->requestAsync($method, $url, $finalOptions);
        }

        try {
            $responses = Promise\Utils::settle($promises)->wait();

            foreach ($responses as $key => $response) {
                if ('fulfilled' === $response['state']) {
                    $results[$key] = $this->handleResponse($response['value']);
                } else {
                    $results[$key] = [
                        'success' => false,
                        'error'   => $response['reason']->getMessage(),
                        'status'  => 0,
                    ];
                }
            }

            return $results;
        } catch (\Exception $e) {
            Log::error('Concurrent request failed', ['error' => $e->getMessage()]);
            throw $e;
        }
    }

    /**
     * 准备请求选项.
     */
    protected function prepareOptions(array $options, ?string $proxy): array
    {
        $defaultOptions = [
            'headers' => array_merge($this->defaultHeaders, $this->headers),
        ];

        if (!empty($this->cookies)) {
            $defaultOptions['cookies'] = $this->cookies;
        }

        if ($proxy && $this->config['proxy_enabled']) {
            $defaultOptions['proxy'] = $proxy;
        }

        return array_merge($defaultOptions, $options);
    }

    /**
     * 处理响应.
     */
    protected function handleResponse($response): array
    {
        $body       = (string) $response->getBody();
        $statusCode = $response->getStatusCode();

        $result = [
            'success' => $statusCode >= 200 && $statusCode < 300,
            'status'  => $statusCode,
            'headers' => $response->getHeaders(),
            'body'    => $body,
        ];

        // 尝试解析 JSON
        if (false !== strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
            $result['data'] = json_decode($body, true) ?? $body;
        } else {
            $result['data'] = $body;
        }

        return $result;
    }

    /**
     * 获取代理地址
     */
    protected function getProxy(): ?string
    {
        if (!$this->config['proxy_enabled']) {
            return null;
        }

        //二次初始化
        empty($this->proxyPool) && $this->initProxyPool();
        if (empty($this->proxyPool)) {
            return null;
        }

        $strategy = $this->config['proxy_strategy'];

        switch ($strategy) {
            case 'round_robin':
                $proxy = $this->proxyPool[$this->proxyIndex % count($this->proxyPool)];
                ++$this->proxyIndex;
                break;

            case 'failover':
                $proxy = $this->proxyPool[$this->proxyIndex % count($this->proxyPool)];
                break;

            case 'random':
            default:
                $proxy = $this->proxyPool[array_rand($this->proxyPool)];
                break;
        }

        $this->currentProxy = $proxy;

        return $proxy;
    }

    /**
     * 记录请求日志.
     */
    protected function logRequest(string $method, string $url, array $options, array $result, float $startTime, ?string $proxy, int $attempt): void
    {
        if (!$this->config['log_enabled']) {
            return;
        }

        $duration   = round((microtime(true) - $startTime) * 1000, 2);
        $statusCode = $result['status'];

        $logData = [
            'method'      => $method,
            'url'         => $url,
            'options'     => $this->filterSensitiveData($options),
            'status'      => $statusCode,
            'duration_ms' => $duration,
            'proxy'       => $proxy,
            'attempt'     => $attempt,
            'success'     => $result['success'],
            'result'      => $result,
        ];

        if (!$result['success']) {
            $logData['error']   = $result['body'] ?? 'Unknown error';
            Log::warning('HTTP Request Failed', $logData);
        } else {
            Log::info('HTTP Request', $logData);
        }
    }

    /**
     * 处理请求错误.
     */
    protected function handleRequestError(string $method, string $url, array $options, \Exception $e, float $startTime, ?string $proxy, int $attempt): void
    {
        $duration = round((microtime(true) - $startTime) * 1000, 2);

        Log::error('HTTP Request Exception', [
            'method'      => $method,
            'url'         => $url,
            'duration_ms' => $duration,
            'proxy'       => $proxy,
            'attempt'     => $attempt,
            'exception'   => $e->getMessage(),
            'options'     => $this->filterSensitiveData($options),
        ]);

        // 标记失败代理
        if ($proxy && 'failover' === $this->config['proxy_strategy']) {
            $this->proxyIndex = ($this->proxyIndex + 1) % count($this->proxyPool);
        }
    }

    /**
     * 过滤敏感数据.
     */
    protected function filterSensitiveData(array $data): array
    {
        //暂不过滤
        return $data;

        /*$sensitiveKeys = ['password', 'token', 'authorization', 'api_key', 'secret', 'cookie'];

        return Arr::map($data, function ($value, $key) use ($sensitiveKeys) {
            if (in_array(strtolower($key), $sensitiveKeys) && is_string($value)) {
                return '***REDACTED***';
            }

            return $value;
        });*/
    }

    /**
     * 设置超时时间.
     */
    public function timeout(int $seconds): self
    {
        $this->config['timeout'] = $seconds;

        return $this;
    }

    /**
     * 设置重试次数.
     */
    public function retry(int $times, int $sleep = 100): self
    {
        $this->config['retry_times'] = $times;
        $this->config['retry_sleep'] = $sleep;

        return $this;
    }

    /**
     * 设置请求头.
     */
    public function withHeaders(array $headers): self
    {
        $this->headers = array_merge($this->headers, $headers);

        return $this;
    }

    /**
     * 设置 Cookie.
     */
    public function withCookies(array $cookies): self
    {
        $this->cookies = array_merge($this->cookies, $cookies);

        return $this;
    }

    /**
     * 设置代理.
     */
    public function withProxy(bool $enabled = true): self
    {
        $this->config['proxy_enabled'] = $enabled;

        return $this;
    }

    /**
     * 设置日志.
     */
    public function withLogging($enabled = true): self
    {
        $this->config['log_enabled'] = $enabled;

        return $this;
    }

    /**
     * 设置基础认证
     */
    public function withBasicAuth(string $username, string $password): self
    {
        $this->headers['Authorization'] = 'Basic ' . base64_encode("$username:$password");

        return $this;
    }

    /**
     * 设置 Bearer Token.
     */
    public function withToken(string $token): self
    {
        $this->headers['Authorization'] = 'Bearer ' . $token;

        return $this;
    }
}
